package org.etnaframework.module.jdbc;

import org.etnaframework.module.base.utils.DbMap;
import org.etnaframework.module.base.utils.EnumWithCode;
import org.etnaframework.module.base.utils.ReflectUtils;
import org.etnaframework.module.jdbc.exception.BeanProcessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 根据bean的字段生成查询结果包装辅助类{@link RowMapper}
 *
 * @author jasyaf
 * @since 2023-12-27
 */
abstract class JdbcRowMapper<T> implements RowMapper<T> {

    /** 目标bean对应的class */
    protected Class<T> clazz;

    /** 从结果集构造对象时需要的字段（表字段名->类字段） */
    protected Map<String, BeanFieldValueSetter> setters;

    private JdbcRowMapper() {
    }

    static <T> RowMapper<T> create(Class<T> clazz) {
        if (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)) {
            throw new BeanProcessException("Collection type " + clazz.getName() + "is not supported");
        }
        if (DbMap.isPrimitiveWrapperType(clazz)) { // 如果是简单封装类型就只生成简单的rowMapper
            return new OneColumnRowMapper<>(clazz);
        }
        Collection<Field> fields = ReflectUtils.getAllFieldsInSourceCodeOrder(clazz, null);
        if (fields.isEmpty()) { // 空类（即没有任何字段的类）是不允许的，会抛出异常
            throw new BeanProcessException("Class " + clazz.getName() + " cannot be empty, it must have at least one field");
        }
        Map<String, BeanFieldValueSetter> setters = new HashMap<>();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) {
                continue; // 不考虑static的或final的
            }
            DbField cc = field.getAnnotation(DbField.class);
            boolean readFromDb = true;
            String name = field.getName();
            EnumConvertMode enumConvertMode = EnumConvertMode.NAME;
            if (null != cc) { // 如果没有加注解就直接使用类字段名作为数据库字段名
                CharSequence str = cc.name();
                name = str == null || str.length() == 0 ? field.getName() : cc.name();
                readFromDb = cc.readFromDb();
                enumConvertMode = cc.enumConvertMode();
            }
            if (!readFromDb) {
                continue;
            }
            Class<?> fieldType = field.getType();
            boolean isEnum = fieldType.isEnum();
            if (!isEnum && DbMap.isNotPrimitiveWrapperType(fieldType)) {
                throw new BeanProcessException(clazz.getName() + "." + field.getName() + " must be 1.Enum types 2.Primitive wrapper type defined in " + DbMap.class.getName() + ". If you don't want to assign a value to it，add @" + DbField.class.getSimpleName() + " and set writeToDb=false");
            }
            // 生成一个赋值工具类，用于给指定javabean的指定字段赋值，规则是如果有set方法就调用set方法，否则直接用反射赋值，出现任何异常将返回null
            BeanFieldValueSetter setter = BeanFieldValueSetter.create(clazz, field, enumConvertMode);
            setters.put(name, setter);
        }
        return new SimpleBeanRowMapper<>(clazz, setters);
    }

    /**
     * 当结果集只有一列时，可用于包装的简单类型
     */
    static class OneColumnRowMapper<T> extends JdbcRowMapper<T> {

        public OneColumnRowMapper(Class<T> clazz) {
            this.clazz = clazz;
        }

        @Override
        public T mapRow(ResultSet rs, int rowNum) throws SQLException {
            Object value = null;
            try {
                value = JdbcUtils.getResultSetValue(rs, 1, clazz);
            } catch (SQLException ex) { // 取值出现问题时，不处理，赋给null
            }
            return clazz.cast(value);
        }
    }

    /**
     * 一个简单的从结果集包装javaBean的包装器类
     */
    static class SimpleBeanRowMapper<T> extends JdbcRowMapper<T> {

        public SimpleBeanRowMapper(Class<T> clazz, Map<String, BeanFieldValueSetter> setters) {
            this.clazz = clazz;
            this.setters = setters;
        }

        @Override
        public T mapRow(ResultSet rs, int rowNum) throws SQLException {
            T result = ReflectUtils.newInstance(clazz);
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                String columnName = JdbcUtils.lookupColumnName(rsmd, i);
                BeanFieldValueSetter setter = setters.get(columnName);
                if (null != setter) { // 由于是从结果集反推到JavaBean的，为了提高性能，建议如果不是完全表对应的话就没有必要用select *来取结果
                    Object value = null;
                    try {
                        value = JdbcUtils.getResultSetValue(rs, i, setter.fieldType);
                    } catch (SQLException ex) { // 取值出现问题时，不处理，赋给null
                    }
                    if (null != value) {
                        setter.setValue(result, value);
                    }
                }
            }
            return result;
        }
    }

    /**
     * 将SQL查询的结果封装成{@link DbMap}，参考自{@link org.springframework.jdbc.core.ColumnMapRowMapper}
     */
    static class ColumnJdbcMapRowMapper extends JdbcRowMapper<DbMap> {

        @Override
        public DbMap mapRow(ResultSet rs, int rowNum) throws SQLException {
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            DbMap dbMapOfColValues = new DbMap();
            for (int i = 1; i <= columnCount; i++) {
                String key = JdbcUtils.lookupColumnName(rsmd, i);
                try {
                    Object obj = JdbcUtils.getResultSetValue(rs, i);
                    dbMapOfColValues.put(key, obj);
                } catch (SQLException ex) { // 如果转换失败就不赋值，如0000-00-00 00:00:00的转换失败问题
                }
            }
            return dbMapOfColValues;
        }
    }

    static abstract class BeanFieldValueSetter {

        /**
         * JavaBean对应字段的类型
         */
        Class<?> fieldType;

        BeanFieldValueSetter(Class<?> fieldType) {
            this.fieldType = fieldType;
        }

        /**
         * 生成一个赋值工具，用于给指定JavaBean的指定字段赋值
         * 规则是如果有set方法就调用set方法，否则直接用反射赋值，出现任何异常将返回null
         */
        static BeanFieldValueSetter create(Class<?> beanClass, Field field, EnumConvertMode enumConvertMode) {
            final Field f = field;
            final Method m = ReflectUtils.getSetMethodByField(beanClass, f);

            boolean useSetter;
            if (null != m) {
                useSetter = true;
                m.setAccessible(true);
            } else {
                useSetter = false;
                f.setAccessible(true);
            }

            if (f.getType().isEnum()) {
                switch (enumConvertMode) {
                    case NAME:
                        return new BeanFieldValueSetter(f.getType()) {

                            @Override
                            public void setValue(Object bean, Object value) {
                                if (null != value) {
                                    Enum<?> e = DbMap.toEnumByName((Class<? extends Enum>) f.getType(), String.valueOf(value), null);
                                    if (null == e) {
                                        throw new IllegalArgumentException("value=" + value + " cannot be converted to enum type " + f.getType().getName());
                                    }
                                    if (useSetter) {
                                        ReflectUtils.invokeMethod(m, bean, e);
                                    } else {
                                        ReflectUtils.setFieldValue(f, bean, e);
                                    }
                                }
                            }
                        };
                    case ORDINAL:
                        return new BeanFieldValueSetter(f.getType()) {

                            @Override
                            public void setValue(Object bean, Object value) {
                                if (null != value) {
                                    Integer valueInt = DbMap.toInt(value.toString(), null);
                                    if (null == valueInt) {
                                        throw new IllegalArgumentException("value=" + value + " is not an ordinal, cannot be converted to enum type " + f.getType().getName());
                                    }
                                    Enum<?> e = DbMap.toEnumByOrdinal((Class<? extends Enum>) f.getType(), valueInt, null);
                                    if (null == e) {
                                        throw new IllegalArgumentException("value=" + value + " cannot be converted to enum type " + f.getType().getName());
                                    }
                                    if (useSetter) {
                                        ReflectUtils.invokeMethod(m, bean, e);
                                    } else {
                                        ReflectUtils.setFieldValue(f, bean, e);
                                    }
                                }
                            }
                        };
                    case CODE:
                        if (!f.getType().isAssignableFrom(EnumWithCode.class)) {
                            throw new IllegalArgumentException("class " + beanClass.getName() + "'s field " + f.getName() + " is not " + EnumWithCode.class.getSimpleName() + ", you cannot use mode " + EnumConvertMode.CODE);
                        }
                        return new BeanFieldValueSetter(f.getType()) {

                            @Override
                            public void setValue(Object bean, Object value) {
                                if (null != value) {
                                    EnumWithCode<?> e = DbMap.toEnumByCode((Class<? extends EnumWithCode>) f.getType(), value.toString(), null);
                                    if (null == e) {
                                        throw new IllegalArgumentException("value=" + value + " cannot be converted to enum type " + f.getType().getName());
                                    }
                                    if (useSetter) {
                                        ReflectUtils.invokeMethod(m, bean, e);
                                    } else {
                                        ReflectUtils.setFieldValue(f, bean, e);
                                    }
                                }
                            }
                        };
                }
            }

            return new BeanFieldValueSetter(f.getType()) {

                @Override
                public void setValue(Object bean, Object value) {
                    if (useSetter) {
                        ReflectUtils.invokeMethod(m, bean, value);
                    } else {
                        ReflectUtils.setFieldValue(f, bean, value);
                    }
                }
            };
        }

        /**
         * 给指定对象的字段赋值
         *
         * @param bean  目标对象
         * @param value 赋值内容
         */
        abstract void setValue(Object bean, Object value);
    }
}
